﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using WCell.MPQTool;
using WCell.Util.Graphics;

namespace WCell.Tools.Maps
{
    public static class WDTParser
    {
        public static WDTFile Process(MpqManager manager, string dataDirectory, string wdtName)
        {
            var wdtFilePath = Path.Combine(dataDirectory, wdtName + ".wdt");
            if (!manager.FileExists(wdtFilePath)) return null;

            var fileReader = new BinaryReader(manager.OpenFile(wdtFilePath));

            var wdt = new WDTFile
            {
                Name = wdtName,
                Path = dataDirectory
            };

            ReadMVER(fileReader, wdt);
            ReadMPHD(fileReader, wdt);
            ReadMAIN(fileReader, wdt);

            if ((wdt.Header.Header1 & WDTFlags.GlobalWMO) != 0)
            {
                // No terrain, the map is a "global" wmo
                // MWMO and MODF chunks follow
                ReadMWMO(fileReader, wdt);
                ReadMODF(fileReader, wdt);
            }

            fileReader.Close();
            return wdt;
        }

        static void ReadMVER(BinaryReader fileReader, WDTFile wdt)
        {
            var type = fileReader.ReadUInt32();
            var size = fileReader.ReadUInt32();
            wdt.Version = fileReader.ReadInt32();
        }

        static void ReadMPHD(BinaryReader fileReader, WDTFile wdt)
        {
            var type = fileReader.ReadUInt32();
            var size = fileReader.ReadUInt32();
            wdt.Header.Header1 = (WDTFlags)fileReader.ReadInt32();
            wdt.Header.Header2 = fileReader.ReadInt32();
            wdt.Header.Header3 = fileReader.ReadInt32();
            wdt.Header.Header4 = fileReader.ReadInt32();
            wdt.Header.Header5 = fileReader.ReadInt32();
            wdt.Header.Header6 = fileReader.ReadInt32();
            wdt.Header.Header7 = fileReader.ReadInt32();
            wdt.Header.Header8 = fileReader.ReadInt32();
        }

        static void ReadMAIN(BinaryReader fileReader, WDTFile wdt)
        {
            var type = fileReader.ReadUInt32();
            var size = fileReader.ReadUInt32();

            for (var y = 0; y < 64; y++)
            {
                for (var x = 0; x < 64; x++)
                {
                    wdt.TileProfile[x, y] = (fileReader.ReadInt64() != 0);
                }
            }
        }

        static void ReadMWMO(BinaryReader fileReader, WDTFile wdt)
        {
            var type = fileReader.ReadUInt32();
            var size = fileReader.ReadUInt32();

            var endPos = fileReader.BaseStream.Position + size;
            while (fileReader.BaseStream.Position < endPos)
            {
                if (fileReader.PeekByte() == 0)
                {
                    fileReader.BaseStream.Position++;
                }
                else
                {
                    wdt.ObjectFiles.Add(fileReader.ReadCString());
                }
            }
        }

        static void ReadMODF(BinaryReader fileReader, WDTFile wdt)
        {
            var type = fileReader.ReadUInt32();
            var size = fileReader.ReadUInt32();

            var endPos = fileReader.BaseStream.Position + size;
            while (fileReader.BaseStream.Position < endPos)
            {
                var objectDef = new MapObjectDefinition();
                var nameIndex = fileReader.ReadInt32(); // 4 bytes
                objectDef.FileName = wdt.ObjectFiles[nameIndex];
                objectDef.UniqueId = fileReader.ReadUInt32(); // 4 bytes
                objectDef.Position = fileReader.ReadVector3(); // 12 bytes
                objectDef.OrientationA = fileReader.ReadSingle(); // 4 Bytes
                objectDef.OrientationB = fileReader.ReadSingle(); // 4 Bytes
                objectDef.OrientationC = fileReader.ReadSingle(); // 4 Bytes
                objectDef.Extents = fileReader.ReadBoundingBox(); // 12*2 bytes
                objectDef.Flags = fileReader.ReadUInt16(); // 2 bytes
                objectDef.DoodadSet = fileReader.ReadUInt16(); // 2 bytes
                objectDef.NameSet = fileReader.ReadUInt16(); // 2 bytes
                fileReader.ReadUInt16(); // padding

                wdt.ObjectDefinitions.Add(objectDef);
            }
        }

        public static byte PeekByte(this BinaryReader binReader)
        {
            byte b = binReader.ReadByte();
            binReader.BaseStream.Position -= 1;
            return b;
        }

        public static bool HasData(this BinaryReader br)
        {
            return br.BaseStream.Position < br.BaseStream.Length;
        }

        public static string ReadCString(this BinaryReader binReader)
        {
            StringBuilder sb = new StringBuilder();
            byte c;

            while ((c = binReader.ReadByte()) != 0)
            {
                sb.Append((char)c);
            }

            return sb.ToString();
        }

        public static BoundingBox ReadBoundingBox(this BinaryReader br)
        {
            return new BoundingBox(br.ReadVector3(), br.ReadVector3());
        }

        public static Vector3 ReadVector3(this BinaryReader br)
        {
            return new Vector3(br.ReadSingle(), br.ReadSingle(), br.ReadSingle());
        }

        public static Plane ReadPlane(this BinaryReader br)
        {
            return new Plane(br.ReadVector3(), br.ReadSingle());
        }

        public static string ReadFixedString(this BinaryReader br, int size)
        {
            var bytes = br.ReadBytes(size);

            for (int i = 0; i < size; i++)
            {
                if (bytes[i] == 0)
                {
                    return Encoding.ASCII.GetString(bytes, 0, i);
                }
            }

            return Encoding.ASCII.GetString(bytes);
        }

        public static Quaternion ReadQuaternion(this BinaryReader br)
        {
            return new Quaternion(br.ReadVector3(), br.ReadSingle());
        }
    }
}
